<template>
  <div style="position: relative">
    <div
      v-if="type === '2'"
      class="verify-img-out"
      :style="{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }"
    >
      <div
        class="verify-img-panel"
        :style="{
          width: setSize.imgWidth,
          height: setSize.imgHeight,
        }"
      >
        <img
          :src="backImgBase ? 'data:image/png;base64,' + backImgBase : defaultImg"
          alt=""
          style="width: 100%; height: 100%; display: block"
        />
        <div v-show="showRefresh" class="verify-refresh" @click="refresh">
          <i class="iconfont icon-refresh" />
        </div>
        <transition name="tips">
          <span v-if="tipWords" class="verify-tips" :class="passFlag ? 'suc-bg' : 'err-bg'">
            {{ tipWords }}
          </span>
        </transition>
      </div>
    </div>
    <!-- 公共部分 -->
    <div
      ref="barArea"
      class="verify-bar-area"
      :style="{
        width: setSize.imgWidth,
        height: barSize.height,
        'line-height': barSize.height,
      }"
    >
      <span class="verify-msg" v-text="text" />
      <div
        class="verify-left-bar"
        :style="{
          width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
          height: barSize.height,
          'border-color': leftBarBorderColor,
          transition: transitionWidth,
        }"
      >
        <span class="verify-msg" v-text="finishText" />
        <div
          class="verify-move-block"
          :style="{
            width: barSize.height,
            height: barSize.height,
            'background-color': moveBlockBackgroundColor,
            left: moveBlockLeft,
            transition: transitionLeft,
          }"
          @touchstart="start"
          @mousedown="start"
        >
          <i :class="['verify-icon iconfont', iconClass]" :style="{ color: iconColor }" />
          <div
            v-if="type === '2'"
            class="verify-sub-block"
            :style="{
              width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',
              height: setSize.imgHeight,
              top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
              'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
            }"
          >
            <img
              :src="'data:image/png;base64,' + blockBackImgBase"
              alt=""
              style="width: 100%; height: 100%; display: block"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref, watch } from 'vue'
import { aesEncrypt, resetSize } from '@/utils/AJCaptcha'
import { get, check } from '@/api/captchaController'

interface ImgSize {
  width: string
  height: string
}
interface BlockSize {
  width: string
  height: string
}
interface BarSize {
  width: string
  height: string
}

const props = withDefaults(
  defineProps<{
    captchaType: string
    type?: string
    mode?: string
    vSpace?: number
    explain?: string
    imgSize?: ImgSize
    blockSize?: BlockSize
    barSize?: BarSize
    defaultImg?: string
  }>(),
  {
    type: '1',
    mode: 'fixed',
    vSpace: 5,
    explain: '向右滑动完成验证',
    imgSize: () => ({ width: '310px', height: '155px' }),
    blockSize: () => ({ width: '50px', height: '50px' }),
    barSize: () => ({ width: '310px', height: '40px' }),
    defaultImg: '',
  },
)

const emit = defineEmits<{
  (e: 'success', payload: any): void
  (e: 'error', payload: any): void
}>()

const secretKey = ref('')
const passFlag = ref(false)
const backImgBase = ref('')
const blockBackImgBase = ref('')
const backToken = ref('')
const startMoveTime = ref(0)
const endMovetime = ref(0)
const tipWords = ref('')
const text = ref('')
const finishText = ref('')
const showRefresh = ref(true)

const setSize = reactive({
  imgHeight: '0px',
  imgWidth: '0px',
  barHeight: '0px',
  barWidth: '0px',
})

const moveBlockLeft = ref<string | undefined>(undefined)
const leftBarWidth = ref<string | undefined>(undefined)
const moveBlockBackgroundColor = ref<string | undefined>(undefined)
const leftBarBorderColor = ref('#ddd')
const iconColor = ref<string | undefined>(undefined)
const iconClass = ref('icon-right')
const status = ref(false)
const isEnd = ref(false)
const transitionLeft = ref('')
const transitionWidth = ref('')

const startLeft = ref(0)
const barArea = ref<HTMLElement | null>(null)

const init = () => {
  text.value = props.explain
  getPicture()

  nextTick(() => {
    const newSize = resetSize(props)
    Object.assign(setSize, newSize)
  })

  window.removeEventListener('touchmove', move as any)
  window.removeEventListener('mousemove', move as any)
  window.removeEventListener('touchend', end as any)
  window.removeEventListener('mouseup', end as any)

  window.addEventListener('touchmove', move as any)
  window.addEventListener('mousemove', move as any)
  window.addEventListener('touchend', end as any)
  window.addEventListener('mouseup', end as any)
}

const start = (e: MouseEvent | TouchEvent) => {
  if (!barArea.value) return

  const clientX = 'touches' in e ? e.touches[0].pageX : (e as MouseEvent).clientX
  startLeft.value = Math.floor(clientX - barArea.value.getBoundingClientRect().left)
  startMoveTime.value = Date.now()

  if (!isEnd.value) {
    text.value = ''
    moveBlockBackgroundColor.value = '#337ab7'
    leftBarBorderColor.value = '#337AB7'
    iconColor.value = '#fff'
    e.stopPropagation()
    status.value = true
  }
}

const move = (e: MouseEvent | TouchEvent) => {
  if (!status.value || isEnd.value || !barArea.value) return

  const clientX = 'touches' in e ? e.touches[0].pageX : (e as MouseEvent).clientX
  const barAreaLeft = barArea.value.getBoundingClientRect().left
  let moveBlockLeftVal = clientX - barAreaLeft

  const maxLeft = barArea.value.offsetWidth - parseInt(props.blockSize.width) / 2 - 2
  if (moveBlockLeftVal >= maxLeft) {
    moveBlockLeftVal = maxLeft
  }
  if (moveBlockLeftVal <= 0) {
    moveBlockLeftVal = parseInt(props.blockSize.width) / 2
  }

  moveBlockLeft.value = moveBlockLeftVal - startLeft.value + 'px'
  leftBarWidth.value = moveBlockLeftVal - startLeft.value + 'px'
}

const end = async () => {
  endMovetime.value = Date.now()

  if (!status.value || isEnd.value) return

  const moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', '')) || 0
  const normalizedDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth)

  const data = {
    captchaType: props.captchaType,
    pointJson: secretKey.value
      ? aesEncrypt(JSON.stringify({ x: normalizedDistance, y: 5.0 }), secretKey.value)
      : JSON.stringify({ x: normalizedDistance, y: 5.0 }),
    token: backToken.value,
  }
  //校验
  try {
    const res = await check(data)
    if (res.data.repCode === '0000') {
      moveBlockBackgroundColor.value = '#5cb85c'
      leftBarBorderColor.value = '#5cb85c'
      iconColor.value = '#fff'
      iconClass.value = 'icon-check'
      showRefresh.value = false
      isEnd.value = true
      passFlag.value = true

      tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s验证成功`

      const captchaVerification = secretKey.value
        ? aesEncrypt(
            backToken.value + '---' + JSON.stringify({ x: normalizedDistance, y: 5.0 }),
            secretKey.value,
          )
        : backToken.value + '---' + JSON.stringify({ x: normalizedDistance, y: 5.0 })

      setTimeout(() => {
        tipWords.value = ''
        emit('success', { captchaVerification })
      }, 1000)
    } else {
      moveBlockBackgroundColor.value = '#d9534f'
      leftBarBorderColor.value = '#d9534f'
      iconColor.value = '#fff'
      iconClass.value = 'icon-close'
      passFlag.value = false

      setTimeout(() => {
        refresh()
      }, 1000)

      emit('error', {})
      tipWords.value = '验证失败'

      setTimeout(() => {
        tipWords.value = ''
      }, 1000)
    }
  } catch (error) {
    console.error('验证请求失败:', error)
  }

  status.value = false
}

const refresh = () => {
  showRefresh.value = true
  finishText.value = ''

  transitionLeft.value = 'left .3s'
  moveBlockLeft.value = '0px'

  leftBarWidth.value = undefined
  transitionWidth.value = 'width .3s'

  leftBarBorderColor.value = '#ddd'
  moveBlockBackgroundColor.value = '#fff'
  iconColor.value = '#000'
  iconClass.value = 'icon-right'
  isEnd.value = false

  getPicture()

  setTimeout(() => {
    transitionWidth.value = ''
    transitionLeft.value = ''
    text.value = props.explain
  }, 300)
}

const getPicture = async () => {
  const res = await get({ type: props.captchaType })

  if (res.data.repCode === '0000') {
    backImgBase.value = res.data.repData?.originalImageBase64
    blockBackImgBase.value = res.data.repData?.jigsawImageBase64
    backToken.value = res.data.repData?.token
    secretKey.value = res.data.repData?.secretKey
  } else {
    tipWords.value = res.data?.repMsg || ''
  }

  if (res.data.repCode === '6201') {
    backImgBase.value = ''
    blockBackImgBase.value = ''
  }
}

// 暴露方法给父组件
const getCurrentInstance = () => ({
  refresh,
})

watch(
  () => props.type,
  () => {
    init()
  },
  { immediate: true },
)

onMounted(() => {
  nextTick(() => {
    barArea.value = document.querySelector('.verify-bar-area') as HTMLElement
  })
})

defineExpose({
  refresh,
})
</script>

<style scoped>
.verify-img-out {
  position: relative;
}

.verify-img-panel {
  position: relative;
  border: 1px solid #ddd;
  background-repeat: no-repeat;
}

.verify-refresh {
  position: absolute;
  right: 0;
  top: 0;
  width: 30px;
  height: 30px;
  cursor: pointer;
  background: rgba(0, 0, 0, 0.3);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
}

.verify-refresh .iconfont {
  color: #fff;
  font-size: 16px;
}

.verify-tips {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 30px;
  line-height: 30px;
  color: #fff;
  text-align: center;
}

.suc-bg {
  background-color: rgba(92, 184, 92, 0.5);
}

.err-bg {
  background-color: rgba(217, 83, 79, 0.5);
}

.tips-enter,
.tips-leave-to {
  bottom: -30px;
}

.tips-enter-active,
.tips-leave-active {
  transition: bottom 0.5s;
}

.verify-bar-area {
  position: relative;
  background: #ffffff;
  text-align: center;
  box-sizing: content-box;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.verify-move-block {
  position: absolute;
  top: 0;
  left: 0;
  background: #fff;
  cursor: pointer;
  box-sizing: content-box;
  box-shadow: 0 0 2px #888888;
  border-radius: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.verify-move-block:hover {
  background-color: #337ab7;
  color: #ffffff;
}

.verify-left-bar {
  position: absolute;
  top: 0;
  left: 0;
  background: #d1e9fe;
  cursor: pointer;
  box-sizing: content-box;
}

.verify-msg {
  z-index: 3;
  font-size: 12px;
  color: #45494c;
}

.verify-sub-block {
  position: absolute;
  /*  background: url('') no-repeat;*/
}

.verify-icon {
  font-size: 18px;
}
</style>
